package com.google.code.whitetask;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ParallelChainTask implements Task {
  private List<Task> tasks = new ArrayList<Task>();
  private List<Future<Result>> results = new ArrayList<Future<Result>>();
  Map<Task, Exception> failedTasks = new ConcurrentHashMap<Task, Exception>();
  ExecutorService executor = null;

  public ParallelChainTask(Task... taskz) {
    for (Task each : taskz) {
      tasks.add(each);
    }
  }

  public ParallelChainTask(List<Task> tasks) {
    this.tasks = tasks;
  }

  public Result execute(Context context) {
    try {
      results = bringExecutor().invokeAll(makeParallelTasks(context));
      release(bringExecutor());
      return hasFailure(results) ? Result.FAILURE : Result.SUCCESS;
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      throw new RuntimeException(e);
    }
  }

  public void setExecutor(ExecutorService executor) {
    this.executor = executor;
  }

  public ExecutorService bringExecutor() {
    if (this.executor == null) {
      this.executor = new InstantExecutor();
    }

    return this.executor;
  }
  
  public Map<Task, Exception> getFailedTasks() {
    return failedTasks;
  }

  private boolean hasFailure(List<Future<Result>> results) throws InterruptedException, ExecutionException {
    for (Future<Result> each : results) {
      if (each.get() == Result.FAILURE) {
        return true;
      }
    }
    return false;
  }

  private Collection<ParallelTaskRunner> makeParallelTasks(Context context) {
    Collection<ParallelTaskRunner> result = new ArrayList<ParallelTaskRunner>();

    for (Task each : tasks) {
      result.add(new ParallelTaskRunner(each, context));
    }
    return result;
  }

  private void release(ExecutorService executor) {
    if (executor instanceof InstantExecutor) {
      executor.shutdown();
    }
  }

  private class InstantExecutor extends ThreadPoolExecutor {
    private InstantExecutor() {
      super(tasks.size(), tasks.size(), 1L, TimeUnit.SECONDS,
          new LinkedBlockingQueue<Runnable>());
    }
  }

  private class ParallelTaskRunner implements Callable<Result> {
    private Task task;
    private Context context;

    private ParallelTaskRunner(Task task, Context context) {
      this.task = task;
      this.context = context;
    }

    public Result call() throws Exception {
      try {
        return task.execute(context);
      } catch (Exception e) {
        failedTasks.put(task, e);
        return Result.FAILURE;
      }
    }
  }

}
